package com.wdl.webserver.api;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.activation.DataHandler;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import com.wdl.webserver.Utils;
import com.wdl.webserver.ZuulFilters;

import springfox.documentation.annotations.ApiIgnore;


@RestController
public class GeneralApiController {
    private static final Logger LOG = LoggerFactory.getLogger(GeneralApiController.class);

    @Value("${mail.hushitong.host}")
    String mailHost;

    @Value("${mail.hushitong.user}")
    String mailUser;

    @Value("${mail.hushitong.password}")
    String mailPassword;

    @Value("${mail.hushitong.to}")
    String mailTo;

    @Value("${mail.hushitong.cc:}")
    String mailCc;

    @Value("${webserver.upgrade.path:/opt/hushitong/su}")
    String upgradePath;

    Pattern suImageNamePattern = Pattern.compile("^([0-9A-Fa-f]{4})([0-9A-Fa-f]{4})([_A-Za-z]{3}[TtUu]{1}).bin$");

    @ApiIgnore
    @GetMapping(value = "/health")
    public Map<String, String> getHealth() {
        Map<String, String> result = new HashMap<String, String>();
        result.put("status", "UP");
        return result;
    }

    @ApiIgnore
    @PostMapping(value = "/api/upload")
    public ResponseEntity<String> uploadFile(HttpServletRequest request) {
        try {
            Properties props = new Properties();
            props.setProperty("mail.smtp.host", mailHost);
            props.setProperty("mail.smtp.auth", "true");
            props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            props.put("mail.smtp.socketFactory.port", "465");
            props.put("mail.smtp.port", "465");
            props.put("mail.user", mailUser);
            props.put("mail.password", mailPassword);

            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };

            Session session = Session.getInstance(props, authenticator);

            LOG.info("Sending Mail:: mailHost=" + mailHost + ", mailUser=" + mailUser + ", mailPassword=" + mailPassword + ", to=" + mailTo + ", cc=" + mailCc);

            MimeMessage message = new MimeMessage(session) {
            };

            message.setFrom(new InternetAddress(mailUser));
            message.setRecipient(Message.RecipientType.TO, new InternetAddress(mailTo));
            if (mailCc != null && !"".equals(mailCc)) {
              message.setRecipient(Message.RecipientType.CC, new InternetAddress(mailCc));
            }
            message.setSentDate(new Date());

            MultipartHttpServletRequest params=((MultipartHttpServletRequest) request);

            String institution = params.getParameter("institution");
            message.setSubject("用户意见 - " + institution);

            MimeBodyPart text = new MimeBodyPart();

            String suggestion = params.getParameter("message");
            suggestion = suggestion == null ? "" : suggestion;

            String phone = params.getParameter("phone");
            phone = phone == null ? "无" : phone;
            text.setContent(suggestion + "\n\n电话号码：" + phone, "text/html;charset=UTF-8");

            MimeMultipart mp = new MimeMultipart();
            mp.addBodyPart(text);

            List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
            try {
                for (int i = 0; i < files.size(); i++) {
                    MultipartFile file = files.get(i);
                    String fileName = file.getOriginalFilename();

                    MimeBodyPart attachment = new MimeBodyPart();
                    DataHandler dh2 = new DataHandler(file.getBytes(), file.getContentType());
                    attachment.setDataHandler(dh2);
                    attachment.setFileName(fileName);
                    mp.addBodyPart(attachment);
                }
            } catch (Exception e) {
                LOG.info("Failed to attach pictures when sending mail.");
            }

            mp.setSubType("mixed");
            message.setContent(mp);
            Transport.send(message);
        } catch (Exception e) {
            LOG.error("Failed to send mail", e);
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        return new ResponseEntity<>(HttpStatus.OK);
    }

    @ApiIgnore
    @DeleteMapping(value = "/api/su/files/{image:.+}")
    public void removeSuImages(@PathVariable String image, HttpServletRequest request) {
        LOG.info(Utils.getLogPrefix(request) + "Remove SU image {}.", image);

        String institutionName = ZuulFilters.getInstitutionNameFromAuthUser(request);

        if (!ZuulFilters.INST_NAME_FOR_HUSHITONG.equals(institutionName)) {
            throw new AuthenticationServiceException("Not institution Hushitong - [" + institutionName + "]!");
        }

        File file = new File(this.upgradePath, image);
        if (file.exists()) {
            Path path = file.toPath();
            try {
                Files.delete(path);
                LOG.info("File [{}] was rmeoved!", path);
                sendReloadImageRequest();
            } catch (IOException e) {
                LOG.error("Failed to remove file [{}], exception: {}", path, e.getMessage());
            }
        } else {
            LOG.warn("File [{}] doesn't exists!", file.toPath());
        }
    }

    @ApiIgnore
    @GetMapping(value = "/api/su/files")
    public Map<String, Object> getSuImages(HttpServletRequest request) {
        LOG.info(Utils.getLogPrefix(request) + "Get SU images.");

        String institutionName = ZuulFilters.getInstitutionNameFromAuthUser(request);

        if (!ZuulFilters.INST_NAME_FOR_HUSHITONG.equals(institutionName)) {
            throw new AuthenticationServiceException("Not institution Hushitong - [" + institutionName + "]!");
        }

        Map<String, Object> result = new HashMap<String, Object>();

        List<Object> images = new ArrayList<Object>();
        File[] files = new File(this.upgradePath).listFiles();
        if (files == null || files.length == 0) {
            LOG.info("No SU images found under path: {}", this.upgradePath);
            result.put("images", images);
            return result;
        }

        for (File file : files) {
            Map<String, Object> image = new HashMap<String, Object>();
            if (file.isDirectory()) {
                continue;
            }
            String name = file.getName();
            // must match pattern 01020304_GLU.bin
            Matcher m = suImageNamePattern.matcher(name); 
            if (m.find()) {
                image.put("imageName", name);
                image.put("imageType", m.group(1));
                image.put("imageVersion", m.group(2));

                Path path = file.toPath();
                BasicFileAttributes fatr;
                try {
                    fatr = Files.readAttributes(path, BasicFileAttributes.class);
                    image.put("updateTime", fatr.lastModifiedTime().toMillis());
                } catch (IOException e) {
                    LOG.error("Failed to read file attribute for {}, exception {}", path, e.getMessage());
                    image.put("updateTime", 0);
                }
                images.add(image);
            }
        }

        result.put("images", images);

        return result;
    }

    @ApiIgnore
    @PostMapping(value = "/api/su/upgrade")
    public ResponseEntity<String> receiveUpgradeFile(@RequestParam("file0") MultipartFile file, HttpServletRequest request) {
        LOG.info(Utils.getLogPrefix(request) + "Upload SU image [" + file.getOriginalFilename() + "].");

        String institutionName = ZuulFilters.getInstitutionNameFromAuthUser(request);

        if (!ZuulFilters.INST_NAME_FOR_HUSHITONG.equals(institutionName)) {
            throw new AuthenticationServiceException("Not institution Hushitong - [" + institutionName + "]!");
        }

        if (file.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        String fileName = file.getOriginalFilename();
        Matcher m = suImageNamePattern.matcher(fileName); 
        if (m.find()) {
            removeOldImages(m.group(1), m.group(2), m.group(3));
        } else {
            LOG.error("File name format is incorrect, skip to save this file. [{}]", fileName);
            sendReloadImageRequest();
            return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
        }

        try {
            byte[] bytes = file.getBytes();
            Path path = Paths.get(this.upgradePath + "/" + fileName);
            Files.write(path, bytes);
        } catch (Exception e) {
            LOG.error("Failed to save file {}, exception: {}", fileName, e);
            sendReloadImageRequest();
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        sendReloadImageRequest();

        return new ResponseEntity<>(HttpStatus.OK);
    }

    private void sendReloadImageRequest() {
        try {
            RestTemplate restTemplate = new RestTemplate();
            HttpHeaders header = new HttpHeaders();
            header.setContentType(MediaType.TEXT_PLAIN);
            HttpEntity<String> entity = new HttpEntity<String>("", header);
            ResponseEntity<String> res = restTemplate.postForEntity("http://localhost:8088/data/suImages", entity, String.class);
            res.getBody();
            LOG.info("Sent request to datarest");
        } catch (Exception e) {
            LOG.info("Sent request to datarest: {}", e.getMessage());
        }
        return;
    }

    private void removeOldImages(String imageType, String imageVersion, String imageProtocol) {
        File[] files = new File(this.upgradePath).listFiles();
        if (files == null || files.length == 0) {
            return;
        }

        for (File file : files) {
            if (file.isDirectory()) {
                continue;
            }
            String name = file.getName();
            // must match pattern 01020304_GLU.bin
            Matcher m = suImageNamePattern.matcher(name); 
            if (m.find()) {
                String type = m.group(1);
                String protocol = m.group(3);
                if (imageType.equals(type) && imageProtocol.equals(protocol)) {
                    // Got one file, remove it!
                    Path path = file.toPath();
                    try {
                        Files.delete(path);
                        LOG.info("Old file [{}] was rmeoved!", path);
                    } catch (IOException e) {
                        LOG.error("Failed to remove file [{}], exception: {}", path, e.getMessage());
                    }
                }
            }
        }
    }
}
